package com.springsummer.information.green.beans;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry {

    private static Logger logger = Logger.getLogger(DefaultListableBeanFactory.class.getName());

    private List<String> beanDefinitionNames = new ArrayList<>(256);
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    private List<String> singletonObjectNames = new ArrayList<>();

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);


    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();


    @Override
    public Object getBean(String beanName) throws BeansException {
        return doGetBean(beanName, null, null, false);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return doGetBean(name, requiredType, null, false);
    }

//    @Override
//    public Object getBean(String name) throws org.springframework.beans.BeansException {
//        return doGetBean(name, null, null, false);
//    }
//
//    @Override
//    public <T> T getBean(String name, Class<T> requiredType) throws org.springframework.beans.BeansException {
//        return doGetBean(name, requiredType, null, false);
//    }

    protected <T> T doGetBean(
            String beanName, Class<T> requiredType, Object[] args, boolean typeCheckOnly)
            throws BeansException {
        Object beanInstance = null;

        // Object singleton = singletons.get(beanName);
        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingletonInstance(beanName);
        if (sharedInstance != null) {
            beanInstance = sharedInstance;
        } else {
            //
            try {

                // Create bean instance.

                BeanDefinition mbd = getMergedBeanDefinition(beanName);

                if (mbd.isSingleton()) {
                    //create singleton instance.

                    //getSingleton(beanName)
                    sharedInstance = singletonObjects.get(beanName);
                    if (sharedInstance == null) {

                        sharedInstance = createBean(beanName, mbd, args);

                        //registry singleton
                        this.singletonObjectNames.add(beanName);
                        this.singletonObjects.put(beanName, sharedInstance);
                    } else {

                        logger.info("create singleton instance getSingletonInstance name:" + beanName);

                    }


                    beanInstance = sharedInstance;


                } else if (mbd.isPrototype()) {
                    // create a new instance.
                    Object prototypeInstance = null;
                    try {

                        prototypeInstance = createBean(beanName, mbd, args);

                    } catch (Exception ex) {
                        throw new BeansException(ex);
                    }

                    beanInstance = prototypeInstance;
                }


            } catch (Exception ex) {
                throw new BeansException(ex);
            }
        }

        return adaptBeanInstance(beanName, beanInstance, requiredType);
    }

    <T> T adaptBeanInstance(String name, Object bean, Class<?> requiredType) {
        if (requiredType != null) {
            try {
                //Casts an object to the class or interface represented by this Class object.
                Object convertedBean = requiredType.cast(bean);
                logger.info("adaptBeanInstance clazz.cast name:" + name + " requiredType:" + requiredType.getName());

                return (T) convertedBean;
            } catch (Exception ex) {

                throw ex;
            }
        }
        return (T) bean;
    }

    protected Object createBean(String beanName, BeanDefinition mbd, Object[] args)
            throws Exception {
        return doCreateBean(beanName, mbd, args);
    }

    protected Object doCreateBean(String beanName, BeanDefinition mbd, Object[] args)
            throws Exception {
        logger.info("doCreateBean name:" + beanName);

        //instanceWrapper = createBeanInstance(beanName, mbd, args);

        Object instanceBean = createBeanInstance(beanName, mbd);

        //存放到毛胚实例缓存中
        this.earlySingletonObjects.put(beanName, instanceBean);

        // Initialize the bean instance.
        Object exposedObject = instanceBean;
        try {
            populateBean(beanName, mbd, instanceBean);
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        } catch (Exception ex) {
            throw new BeansException(ex);
        }


        return exposedObject;
    }

    protected Object createBeanInstance(String beanName, BeanDefinition beanDefinition) {
        try {
            Object beanInstance;

            //beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);

            Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());

            // 处理构造器参数
            MutableConstructorArgValues argumentValues =
                    beanDefinition.getConstructorArgumentValues();

            //ctor.newInstance(argsWithDefaultValues);
            if (!argumentValues.isEmpty()) {
                Class<?>[] paramTypes = new Class<?>
                        [argumentValues.getArgumentCount()];
                Object[] paramValues = new
                        Object[argumentValues.getArgumentCount()];
                //对每一个参数，分数据类型分别处理
                processConstructorArgsInstantiateBean(argumentValues, paramTypes, paramValues);

                //按照特定构造器创建实例
                Constructor constructor = clazz.getDeclaredConstructor(paramTypes);
                beanInstance = constructor.newInstance(paramValues);

            } else { //如果没有参数，直接创建实例

                beanInstance = clazz.getDeclaredConstructor().newInstance();

            }


            return beanInstance;
        } catch (Throwable ex) {
            throw new BeansException(ex);
        }
    }

    private void processConstructorArgsInstantiateBean(MutableConstructorArgValues argumentValues, Class<?>[] paramTypes, Object[] paramValues) {
        for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
            ConstructorArgValue argumentValue =
                    argumentValues.getIndexedArgumentValue(i);
            if ("String".equals(argumentValue.getType()) ||
                    "java.lang.String".equals(argumentValue.getType())) {
                paramTypes[i] = String.class;
                paramValues[i] = argumentValue.getValue();
            } else if ("Integer".equals(argumentValue.getType()) ||
                    "java.lang.Integer".equals(argumentValue.getType())) {
                paramTypes[i] = Integer.class;
                paramValues[i] =
                        Integer.valueOf((String) argumentValue.getValue());
            } else if ("int".equals(argumentValue.getType())) {
                paramTypes[i] = int.class;
                paramValues[i] = Integer.valueOf((String)
                        argumentValue.getValue());
            } else { //默认为string
                paramTypes[i] = String.class;
                paramValues[i] = argumentValue.getValue();
            }
        }
    }

    protected void populateBean(String beanName, BeanDefinition mbd, Object beanInstance) throws Exception {

        MutablePropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);


        if (pvs != null) {
            applyPropertyValues(beanName, mbd, beanInstance, pvs);
        }

    }

    protected void applyPropertyValues(String beanName, BeanDefinition beanDefinition, Object beanInstance, MutablePropertyValues mutablePropertyValues) throws Exception {
        if (mutablePropertyValues.isEmpty()) {
            return;
        }

        Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());

        for (PropertyValue propValue : mutablePropertyValues.getPropertyValueList()) {

            Method method = null;
            try {
                String pType = propValue.getType();
                String propName = propValue.getName();
                Object propVal = propValue.getValue();
                boolean refFlag = propValue.isRefFlag();

                Class<?>[] paramTypes = new Class<?>[1];
                Object[] paramValues = new Object[1];


                if (refFlag == false) {
                    if ("String".equals(pType) || "java.lang.String".equals(pType)) {
                        paramTypes[0] = String.class;
                    } else if ("Integer".equals(pType) ||
                            "java.lang.Integer".equals(pType)) {
                        paramTypes[0] = Integer.class;
                    } else if ("int".equals(pType)) {
                        paramTypes[0] = int.class;
                    } else { // 默认为string
                        paramTypes[0] = String.class;
                    }

                    paramValues[0] = propVal;
                } else {
                    Class<?> paraClazz = Class.forName(pType);
                    RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) propVal;
                    paramTypes[0] = paraClazz;
                    paramValues[0] = this.getBean(runtimeBeanReference.getBeanName());
                }


                //按照setXxxx规范查找setter方法，调用setter方法设置属性
                String methodName = "set" + propName.substring(0, 1).toUpperCase()
                        + propName.substring(1);


                method = clazz.getDeclaredMethod(methodName, paramTypes);
                if (!method.canAccess(beanInstance)) {
                    method.setAccessible(true);
                }
                method.invoke(beanInstance, paramValues);

            } catch (Exception ex) {
                throw ex;
            }

        }


    }

    protected Object initializeBean(String beanName, Object bean, BeanDefinition mbd) throws Exception {

        try {

            invokeAwareMethods(beanName, bean);


            Object wrappedBean = bean;

            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);


            invokeInitMethods(beanName, wrappedBean, mbd);


            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);


            return wrappedBean;
        } catch (Throwable ex) {
            throw new RuntimeException(ex);
        }


        //return bean;
    }

    private void invokeAwareMethods(String beanName, Object bean) throws Exception {
        if (bean != null) {
            if (bean instanceof BeanNameAware) {
                logger.info("invokeAwareMethods BeanNameAware beanname:" + beanName);
                BeanNameAware beanNameAware = (BeanNameAware) bean;
                beanNameAware.setBeanName(beanName);
            }
            if (bean instanceof BeanFactoryAware) {
                logger.info("invokeAwareMethods BeanFactoryAware beanname:" + beanName);
                BeanFactoryAware beanFactoryAware = (BeanFactoryAware) bean;
                beanFactoryAware.setBeanFactory(this);
            }
        }
    }

    protected void invokeInitMethods(String beanName, Object bean, BeanDefinition beanDefinition)
            throws Throwable {

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean) {
            InitializingBean initializingBean = (InitializingBean) bean;

            initializingBean.afterPropertiesSet();

        }

        if (beanDefinition != null) {
            String initMethodName = beanDefinition.getInitMethodName();
            if (null != initMethodName && !"".equals(initMethodName)) {
                //invokeCustomInitMethod(beanName, bean, mbd);
                Class<?> clz = Class.forName(beanDefinition.getBeanClassName());
                Method method = null;
                try {
                    method = clz.getMethod(beanDefinition.getInitMethodName());
                    method.invoke(bean);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    protected BeanDefinition getMergedBeanDefinition(String beanName) throws Exception {
        return getBeanDefinition(beanName);
    }

    protected Object getSingletonInstance(String beanName) {
        Object singleton = singletonObjects.get(beanName);
        if (singleton == null) {
            singleton = this.earlySingletonObjects.get(beanName);
        }

        if (singleton != null) {
            logger.info("getSingletonInstance name:" + beanName);
        }
        return singleton;
    }

    @Override
    public boolean containsBean(String name) {
        return containsSingleton(name);
    }


    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws Exception {
        if (containsBeanDefinition(beanName)) {
            throw new BeansException("duplicate beanname registerBeanDefinition");
        }
        this.beanDefinitionNames.add(beanName);
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new RuntimeException("nosuch BeanDefinition beanName:" + beanName);
        }
        return beanDefinition;
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        this.beanDefinitionNames.remove(beanName);
        this.beanDefinitionMap.remove(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionNames.toArray(new String[beanDefinitionNames.size()]);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public boolean isSingleton(String name) {
        try {
            return getBeanDefinition(name).isSingleton();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean isPrototype(String name) {
        try {
            return getBeanDefinition(name).isPrototype();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Class<?> getType(String name) {
        BeanDefinition beanDefinition = null;
        try {
            beanDefinition = getBeanDefinition(name);
            return Class.forName(beanDefinition.getBeanClassName());

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String[] getBeanNamesForType(Class<?> type) {
        List<String> nameArray = new ArrayList<>();
        for (Map.Entry<String, BeanDefinition> entry : this.beanDefinitionMap.entrySet()) {
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getBeanClassName().equals(type.getName())) {
                nameArray.add(beanDefinition.getBeanId());
            }
        }
        return nameArray.toArray(new String[nameArray.size()]);
    }

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjectNames.add(beanName);
            this.singletonObjects.put(beanName, singletonObject);
        }
    }

    @Override
    public boolean containsSingleton(String beanName) {
        return this.singletonObjects.containsKey(beanName);
    }

    protected void removeSingleton(String beanName) {
        synchronized (this.singletonObjects) {
            this.singletonObjectNames.remove(beanName);
            this.singletonObjects.remove(beanName);
        }
    }

    @Override
    public Object getSingleton(String beanName) {
        return this.singletonObjects.get(beanName);
    }

    @Override
    public String[] getSingletonNames() {
        return this.singletonObjectNames.toArray(new String[singletonObjectNames.size()]);
    }

    @Override
    public int getSingletonCount() {
        return this.singletonObjects.size();
    }

}

